<?php
/**
 * @Author: laoweizhen <1149243551@qq.com>,
 * @Date: 2022/10/06 13:46,
 * @LastEditTime: 2022/10/06 13:46
 */
declare(strict_types=1);

namespace Zhen\HyperfDevtool\Generator;

use Hyperf\Command\Annotation\Command;
use Hyperf\Command\Command as HyperfCommand;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\CodeGen\Project;
use Hyperf\Utils\Str;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

#[Command]

class InitServiceCommand extends HyperfCommand
{
    // 项目文件
    protected array $projectDirFile = [];

    // 默认目录
    protected array $defaultDirname = [
        'Constants',
        'Model',
        'Dao',
        'Service',
        'Request',
        'Queue\Consumer',
        'Queue\Producer',
    ];

    public function __construct()
    {
        parent::__construct('ext-init:service');
        $this->setDescription('Initialize service directory and file');

        $this->projectDirFile['Service'] = class_exists(\Zhen\HyperfKit\Abstracts\AbstractService::class)
            ? 'service-core.stub' : 'service.stub';
        $this->projectDirFile['Dao'] = class_exists(\Zhen\HyperfKit\Abstracts\AbstractDao::class)
            ? 'dao-core.stub' : 'dao.stub';
    }

    public function handle()
    {
        $module = $this->getModuleInput();
        $modelName = $this->getModelName();

        $this->genFileByAttr($modelName);
        $this->genFileByCommand($module, $modelName);
    }

    public function configure()
    {
        foreach ($this->getArguments() as $argument) {
            $this->addArgument(...$argument);
        }

        foreach ($this->getOptions() as $option) {
            $this->addOption(...$option);
        }
    }

    /**
     * 通过自定义属性生成文件.
     */
    protected function genFileByAttr(string $modelName)
    {
        // 创建默认目录
        foreach ($this->defaultDirname as $dir) {
            $dir = $this->getQualifyNamespace($dir);
            $path = $this->getPath($dir, '', '');
            $this->makeDirectory($path, false);
        }

        // 创建文件
        foreach ($this->projectDirFile as $dir => $stubFileName) {
            $name = $this->qualifyClass($modelName, $dir);
            $path = $this->getPath($name, $dir);
            if (is_file($path)) {
                continue;
            }

            $this->makeDirectory($path);

            file_put_contents($path, $this->buildClass($name, $stubFileName));

            $this->openWithIde($path);
        }
    }

    /**
     * Build the class with the given name.
     *
     * @param string $name
     * @param string $stubFileName stub文件名
     */
    protected function buildClass($name, string $stubFileName): string
    {
        $stub = file_get_contents($this->getStub($stubFileName));

        return $this->replaceNamespace($stub, $name)
            ->replaceModuleName($stub)
            ->replaceModelName($stub)
            ->replaceClass($stub, $name);
    }

    protected function getStub(string $stubFileName): string
    {
        return __DIR__ . '/stubs/' . $stubFileName;
    }

    /**
     * Replace the namespace for the given stub.
     *
     * @param string $stub
     * @param string $name
     * @return $this
     */
    protected function replaceNamespace(&$stub, $name): static
    {
        $stub = str_replace(
            ['%NAMESPACE%'],
            [$this->getNamespace($name)],
            $stub
        );

        return $this;
    }

    /**
     * Replace the module name for the given stub.
     *
     * @param string $stub
     * @param string $name
     * @return $this
     */
    protected function replaceModuleName(&$stub): static
    {
        $stub = str_replace(
            ['%MODULE%'],
            [ucfirst($this->getModuleInput())],
            $stub
        );

        return $this;
    }

    /**
     * Replace the module name for the given stub.
     *
     * @param string $stub
     * @param string $name
     * @return $this
     */
    protected function replaceModelName(&$stub): static
    {
        $modelName = $this->getModelName();
        $stub = str_replace(
            ['%MODEL_CLASS%', '%MODEL_NAME%', '%MODEL_NAME%'],
            [ucfirst(Str::camel($modelName)), lcfirst($modelName)],
            $stub
        );

        return $this;
    }

    /**
     * 通过命令生成文件.
     */
    protected function genFileByCommand(string $module, string $modelName)
    {
        /*$callNames = [
            'ext-gen:controller',
            'ext-gen:model',
            'ext-gen:request',
        ];
        foreach ($callNames as $name) {
            $this->call($name, ['module' => $module, 'name' => $modelName]);
        }*/
        $this->call('ext-gen:controller', ['module' => $module, 'name' => $modelName . 'Controller']);
        $this->call('ext-gen:model', ['module' => $module, 'table' => strtolower(Str::snake($modelName))]);
        $this->call('ext-gen:request', ['module' => $module, 'name' => $modelName . 'Request']);
    }

    /**
     * Parse the class name and format according to the root namespace.
     *
     * @param string $modelName 模型名称
     */
    protected function qualifyClass(string $modelName, string $dirname): string
    {
        $name = ltrim($modelName, '\\/');

        $name = str_replace('/', '\\', $name);

        $namespace = $this->getQualifyNamespace($dirname);

        return $namespace . '\\' . $name;
    }

    protected function getQualifyNamespace(string $dirname): string
    {
        $namespace = $this->input->getOption('namespace');
        if (empty($namespace)) {
            $namespace = $this->getDefaultNamespace($dirname);
        }
        return $namespace;
    }

    /**
     * @param string $dirname 生成目录名称
     */
    protected function getDefaultNamespace(string $dirname): string
    {
        return 'App\\' . $this->getModuleInput() . '\\' . $dirname;
    }

    /**
     * Get the destination class path.
     *
     * @param string $moduleDirName 模块目录名
     * @param mixed $extension
     * @return string
     */
    protected function getPath(string $name, string $moduleDirName, $extension = '.php')
    {
        $project = new Project();
        return BASE_PATH . '/' . $project->path(ucfirst($name) . ucfirst($moduleDirName), $extension);
    }

    protected function getModuleInput(): string
    {
        return ucfirst(Str::camel($this->input->getArgument('module')));
    }

    protected function getModelName(): string
    {
        return ucfirst($this->input->getArgument('model_name'));
    }

    /**
     * Build the directory for the class if necessary.
     *
     * @param string $path
     * @param bool $isFilePath 是否包含文件名的目录
     */
    protected function makeDirectory($path, bool $isFilePath = true): string
    {
        $path = $isFilePath ? dirname($path) : $path;
        if (! is_dir($path)) {
            mkdir($path, 0777, true);
        }

        return $path;
    }

    /**
     * Replace the class name for the given stub.
     *
     * @param string $stub
     * @param string $name
     * @return string
     */
    protected function replaceClass($stub, $name)
    {
        $class = str_replace($this->getNamespace($name) . '\\', '', $name);

        return str_replace('%CLASS%', $class, $stub);
    }

    /**
     * Get the full namespace for a given class, without the class name.
     *
     * @param string $name
     * @return string
     */
    protected function getNamespace($name)
    {
        return trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
    }

    /**
     * Get the console command arguments.
     */
    protected function getArguments(): array
    {
        return [
            ['module', InputArgument::REQUIRED, 'The name of the module'],
            ['model_name', InputArgument::REQUIRED, 'The name of the model'],
        ];
    }

    /**
     * Get the console command options.
     */
    protected function getOptions(): array
    {
        return [
            ['namespace', 'N', InputOption::VALUE_OPTIONAL, 'The namespace for class.', null],
        ];
    }

    protected function getContainer(): ContainerInterface
    {
        return ApplicationContext::getContainer();
    }

    /**
     * Get the editor file opener URL by its name.
     */
    protected function getEditorUrl(string $ide): string
    {
        switch ($ide) {
            case 'sublime':
                return 'subl://open?url=file://%s';
            case 'textmate':
                return 'txmt://open?url=file://%s';
            case 'emacs':
                return 'emacs://open?url=file://%s';
            case 'macvim':
                return 'mvim://open/?url=file://%s';
            case 'phpstorm':
                return 'phpstorm://open?file=%s';
            case 'idea':
                return 'idea://open?file=%s';
            case 'vscode':
                return 'vscode://file/%s';
            case 'vscode-insiders':
                return 'vscode-insiders://file/%s';
            case 'vscode-remote':
                return 'vscode://vscode-remote/%s';
            case 'vscode-insiders-remote':
                return 'vscode-insiders://vscode-remote/%s';
            case 'atom':
                return 'atom://core/open/file?filename=%s';
            case 'nova':
                return 'nova://core/open/file?filename=%s';
            case 'netbeans':
                return 'netbeans://open/?f=%s';
            case 'xdebug':
                return 'xdebug://%s';
            default:
                return '';
        }
    }

    /**
     * Open resulted file path with the configured IDE.
     */
    protected function openWithIde(string $path): void
    {
        $ide = (string) $this->getContainer()->get(ConfigInterface::class)->get('devtool.ide');
        $openEditorUrl = $this->getEditorUrl($ide);

        if (! $openEditorUrl) {
            return;
        }

        $url = sprintf($openEditorUrl, $path);
        switch (PHP_OS_FAMILY) {
            case 'Windows':
                exec('explorer ' . $url);
                break;
            case 'Linux':
                exec('xdg-open ' . $url);
                break;
            case 'Darwin':
                exec('open ' . $url);
                break;
        }
    }
}